#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include "threads.h"
#include "ray.h"		// included here for linker to recognize MAX_OBJECTS
#include "utils.h"

#define MIN(a,b) (a < b ? a : b)
#define EPSILON 1.0E-6f

// globals
const int MAX_OBJECTS = 64;

bool verbose = false;

Object Scene1[MAX_OBJECTS];
Object Scene2[MAX_OBJECTS];

const int SCREEN_WIDTH = 1680;
const int SCREEN_HEIGHT = 1050;

Color *OutputBuffer;

float GetSceneScore (Object *scene)
{
	float add=0, total=0;
	int lights = 0;
	Vector3 forward = {0, 0, 1};
	float x, y, z;

	// get number of lights first
	for (int i=0; i < MAX_OBJECTS; i++)
	{
		if (scene[i].active == false)
			continue;

		if (scene[i].type == t_light)
			lights++;
	}

	// analyze other objects
	for (int i=0; i < MAX_OBJECTS; i++)
	{
		if (scene[i].active == false)
			continue;

		// Each base factor is multiplied by a surface area calculation based on the object's size
		if (scene[i].type == t_light || scene[i].type == t_sphere)
			add = 1.94f * 4*M_PI*scene[i].sphere.radius*scene[i].sphere.radius / 2;
		else if (scene[i].type == t_plane)
			add = 2.894f * fabs(DotProduct(scene[i].plane.normal, forward));
		else if (scene[i].type == t_box)
		{
			x = scene[i].box.x;
			y = scene[i].box.y;
			z = scene[i].box.z;
			add = 6.637f * (x*x + y*y + z*z);
		}
		else if (scene[i].type == t_disc)
			add = 4.846f * M_PI*scene[i].disc.radius*scene[i].disc.radius;

		if (scene[i].reflect >= EPSILON)
			add *= 3;

		total += add;
	}

	return (lights+1) * total;
}

int AssignThreads (int totalThreads)
{
	float score1=0, score2=0, total;
	int threads1, threads2;
	
	score1 = GetSceneScore(Scene1);
	score2 = GetSceneScore(Scene2);
	total = score1 + score2;

	threads1 = (int)(totalThreads * score1 / total);
	threads2 = totalThreads - threads1;

	if (threads1 <= 0)
	{
		threads1 = 1;
		threads2--;
	}
	else if (threads2 <= 0)
	{
		threads2 = 1;
		threads1--;
	}

	return threads1;
}

void RunPostProcessing (void)
{
	int x, y, x2, y2, num;
	float dist1, dist2, d;
	float change;
	int blur;
	Color blurColor;
	Color *BlurBuffer;

	BlurBuffer = new Color[SCREEN_WIDTH*SCREEN_HEIGHT];

	dist1 = OutputBuffer[(SCREEN_HEIGHT/2)*SCREEN_WIDTH + SCREEN_WIDTH/4].dist;
	dist2 = OutputBuffer[(SCREEN_HEIGHT/2)*SCREEN_WIDTH + SCREEN_WIDTH*3/4].dist;

	for (y=0; y < SCREEN_HEIGHT; y++)
	{
		for (x=0; x < SCREEN_WIDTH; x++)
		{
			num = 0;
			blurColor.r = blurColor.g = blurColor.b = 0.0f;
			for (x2 = x-10; x2 <= x+10; x2++)
			{
				if (x2 < 0 || x2 >= SCREEN_WIDTH)
					continue;

				for (y2 = y-10; y2 <= y+10; y2++)
				{
					if (y2 < 0 || y2 >= SCREEN_HEIGHT)
						continue;
		
					d = OutputBuffer[y2*SCREEN_WIDTH+x2].dist;
					change = x < SCREEN_WIDTH/2 ? fabs(dist1-d) : fabs(dist2-d);
					blur = (int)((MIN(1.0f, change / 300.0f)*10.0f)+0.5f);
					if (sqrt((x2-x)*(x2-x) + (y2-y)*(y2-y)) > blur)
						continue;
					//if (d < OutputBuffer[y2*SCREEN_WIDTH+x2].dist)
					//	continue;

					blurColor.r += OutputBuffer[y2*SCREEN_WIDTH+x2].r;
					blurColor.g += OutputBuffer[y2*SCREEN_WIDTH+x2].g;
					blurColor.b += OutputBuffer[y2*SCREEN_WIDTH+x2].b;
					num++;
				}
			}
			blurColor.r /= (float)num;
			blurColor.g /= (float)num;
			blurColor.b /= (float)num;

			BlurBuffer[y*SCREEN_WIDTH+x].r = blurColor.r;
			BlurBuffer[y*SCREEN_WIDTH+x].g = blurColor.g;
			BlurBuffer[y*SCREEN_WIDTH+x].b = blurColor.b;
		}
	}

	delete[] OutputBuffer;
	OutputBuffer = BlurBuffer;
}

int main (int argc, char *argv[])
{
	int numThreads = 0;
	int threads1;
	bool evenDist = false;
	bool blur = true;

	if (argc > 1)
		numThreads = atoi(argv[1]);
	if (numThreads <= 1)
		numThreads = 2;

	if (argc > 2)
		InitObjectsFromFile(argv[2]);
	else if (!isatty(fileno(stdin)))		// Unix only - if stdin is not a terminal
		InitObjectsFromFile("-");
	else
		InitObjects();

	for (int i=0; i < argc; i++)
	{
		if (!strcmp(argv[i], "-verbose"))
			verbose = true;
		else if (!strcmp(argv[i], "-even"))
			evenDist = true;
		else if (!strcmp(argv[i], "-noblur"))
			blur = false;
	}

	OutputBuffer = new Color[SCREEN_WIDTH*SCREEN_HEIGHT];
	
	if (evenDist)
		threads1 = numThreads/2;
	else
		threads1 = AssignThreads(numThreads);

	if (verbose)
		printf("Assigning %d threads to 1, %d threads to 2.\n", threads1, numThreads - threads1);
	MakeThreads(threads1, 1, BYPIXEL);			// scene 1
	MakeThreads(numThreads - threads1, 2, BYPIXEL);		// scene 2

	if (blur)
		RunPostProcessing();

	if (verbose)
		printf("Write\n");		
	WriteTGAFile ("output.tga");
	delete[] OutputBuffer;

	if (verbose)
		printf("Done\n");
	
	return EXIT_SUCCESS;
}


